use std::{collections::BTreeMap, io::Read, path::PathBuf, str::FromStr, sync::Arc};

use serde_json::json;
use tauri::AppHandle;
use tauri_plugin_opener::OpenerExt;

use crate::{
    get_root, helper,
    models::{Preference, TransactionTemplate},
    pool,
    secs::ConnectionOptions,
    AppError, AppResult,
};

#[derive(serde::Serialize, serde::Deserialize)]
pub(crate) struct SMLFile {
    path: String,
    // content: String,
    configuration: ConnectionOptions,
    preference: Preference,
    transactions: serde_json::Value,
}

#[derive(serde::Serialize)]
pub(crate) struct SMLResult<T> {
    #[serde(rename = "isOpen")]
    is_open: bool,
    data: T,
}

#[tauri::command]
pub(crate) async fn sml_load(id: u64, path: Option<String>) -> AppResult<SMLFile> {
    if let Some(path) = path {
        load_sml_from_file(id, std::path::PathBuf::from(path)).await
    } else {
        load_sml_from_file(id, get_default_sml_path(id).await).await
    }
}

async fn get_default_sml_path(id: u64) -> PathBuf {
    let guard = pool::ALL_CONNS.read().await;
    match guard.get(&id) { Some(connection) => {
        if connection.sml_path.capacity() > 0 {
            connection.sml_path.clone()
        } else {
            get_root().join("conf").join("Default.json")
        }
    } _ => {
        get_root().join("conf").join("Default.json")
    }}
}

#[tauri::command]
pub(crate) async fn sml_save(id: u64, path: Option<String>) -> AppResult<()> {
    let mut save_path = path.map(|s| PathBuf::from(s));
    let mut json = json!({});
    let guard = pool::ALL_CONNS.read().await;
    if let Some(connection) = guard.get(&id) {
        json["configuration"] = serde_json::to_value(&connection.configuration)
            .or_else(|err| Err(AppError::Message(err.to_string())))?;
        json["preference"] = serde_json::to_value(&connection.preference)
            .or_else(|err| Err(AppError::Message(err.to_string())))?;
        json["mappings"] = serde_json::Value::Array(vec![]);
        let mut json_transactions = vec![];
        for item in &connection.transactions {
            json_transactions.push(item.to_json_without_id());
        }
        json["transactions"] = serde_json::Value::Array(json_transactions);
        if save_path.is_none() {
            save_path.replace(connection.sml_path.clone());
        }
    }
    drop(guard);
    if let Some(save_path) = save_path {
        if let Some(parent) = save_path.parent() {
            if !parent.exists() {
                std::fs::create_dir_all(parent)
                    .or_else(|err| Err(AppError::Message(format!("{err:?}"))))?;
            }
        }
        if let Ok(str_json) = serde_json::to_string_pretty(&json) {
            std::fs::write(save_path, str_json)
                .or_else(|err| Err(AppError::Message(format!("{err:?}"))))?;
        }
        Ok(())
    } else {
        Err(AppError::Message("the path to save is invalid".to_string()))
    }
}

async fn load_sml_from_file(id: u64, path: PathBuf) -> AppResult<SMLFile> {
    let mut file = std::fs::File::open(&path)
        .or_else(|err| Err(AppError::Message(format!("{err:?}, {path:?}"))))?;
    if let Ok(mt) = file.metadata() {
        if mt.len() > 21474836480 {
            return Err(AppError::Message(
                "the file is too large, limit is 20MB".to_string(),
            ));
        }
    }
    let mut content = String::new();
    file.read_to_string(&mut content)
        .or_else(|err| Err(AppError::Message(format!("{err:?}, {path:?}"))))?;
    let mut json_content = serde_json::from_str::<serde_json::Value>(&content)
        .or_else(|err| Err(AppError::Message(format!("{err:?}"))))?;
    let json_configuration = match json_content.get("configuration") {
        Some(val) => val,
        None => {
            return Err(AppError::Message(
                "couldn't find configuration property".to_string(),
            ));
        }
    };
    let mut configuration = serde_json::from_value::<ConnectionOptions>(json_configuration.clone())
        .or_else(|err| {
            Err(AppError::Message(format!(
                "parse connection to obj failed, {err}"
            )))
        })?;
    // 检查 identity 是否存在，如果存在，自动重命名
    {
        let guard = pool::ALL_CONNS.read().await;
        check_configuration_identity(&*guard, &id, &mut configuration);
        drop(guard);
    }
    let json_preference = match json_content.get("preference") {
        Some(val) => val,
        None => {
            return Err(AppError::Message(
                "couldn't find preference property".to_string(),
            ));
        }
    };
    let mut preference =
        serde_json::from_value::<Preference>(json_preference.clone()).or_else(|err| {
            Err(AppError::Message(format!(
                "parse preference to obj failed, {err}"
            )))
        })?;
    for item in preference.collection_event.items.iter_mut() {
        item.key = crate::get_unique_id();
    }
    let json_transactions = match json_content.get_mut("transactions") {
        Some(val) => val,
        None => {
            return Err(AppError::Message(
                "couldn't find transactions property".to_string(),
            ));
        }
    };
    if let serde_json::Value::Array(array) = json_transactions {
        for item in array.iter_mut() {
            item["id"] = serde_json::Value::String(helper::get_transaction_id());
        }
    }
    let transactions = serde_json::from_value::<Vec<TransactionTemplate>>(
        json_transactions.clone(),
    )
    .or_else(|err| {
        Err(AppError::Message(format!(
            "parse transactions to obj failed, {err}"
        )))
    })?;
    let mut guard = pool::ALL_CONNS.write().await;
    if let Some(connection) = guard.get_mut(&id) {
        connection.configuration = configuration.clone();
        connection.preference = Arc::new(preference.clone());
        connection.transactions = transactions;
        if let Some(logger) = connection.logger.take() {
            drop(logger);
            match PathBuf::from_str(&connection.preference.log.dir) {
                Ok(mut log_dir) => {
                    crate::fill_path_internal(&mut log_dir);
                    let logger = crate::logger::Logger::new(
                        log_dir,
                        connection.configuration.identity.clone(),
                        connection.preference.log.file_max_size * 1024 * 1024,
                        connection.preference.log.is_write_secs1,
                    );
                    connection.logger.replace(logger);
                }
                Err(_) => {}
            }
        }
    }
    drop(guard);
    Ok(SMLFile {
        path: path.to_string_lossy().to_string(),
        configuration,
        preference,
        transactions: json_transactions.clone(),
    })
}

///
/// action   Update = 0, InsertBefore = 1, InsertAfter = 2, Append = 3,
///
///
#[tauri::command]
pub(crate) async fn sml_sync_transaction(
    id: u64,
    tx_id: String,
    mut transaction: TransactionTemplate,
    action: u8,
) -> AppResult<String> {
    let mut guard = pool::ALL_CONNS.write().await;
    if let Some(connection) = guard.get_mut(&id) {
        if action == 0 {
            for tx in connection.transactions.iter_mut() {
                if tx.id == tx_id {
                    tx.name = transaction.name;
                    tx.desc = transaction.desc;
                    tx.primary = transaction.primary;
                    tx.secondary = transaction.secondary;
                    tx.match_rules = transaction.match_rules;
                    tx.reply_secondary = transaction.reply_secondary;
                    tx.send_primary = transaction.send_primary;
                    return Ok(String::new());
                }
            }
        } else if action == 1 {
            let mut target_index = None;
            for (index, val) in connection.transactions.iter().enumerate() {
                if val.id == tx_id {
                    target_index = Some(index)
                }
            }
            if let Some(target_index) = target_index {
                let id = helper::get_transaction_id();
                transaction.id = id.clone();
                connection.transactions.insert(target_index, transaction);
                return Ok(id);
            } else {
                return Err(AppError::Message("couldn't find transaction".to_string()));
            }
        } else if action == 2 {
            let mut target_index = None;
            for (index, val) in connection.transactions.iter().enumerate() {
                if val.id == tx_id {
                    target_index = Some(index)
                }
            }
            if let Some(target_index) = target_index {
                let id = helper::get_transaction_id();
                transaction.id = id.clone();
                connection
                    .transactions
                    .insert(target_index + 1, transaction);
                return Ok(id);
            } else {
                return Err(AppError::Message("couldn't find transaction".to_string()));
            }
        } else if action == 3 {
            let id = helper::get_transaction_id();
            transaction.id = id.clone();
            connection.transactions.push(transaction);
            return Ok(id);
        }
    }
    Err(AppError::Message(format!("unknow action = {action}")))
}

#[tauri::command]
pub(crate) async fn sml_del_transaction(id: u64, tx_id: String) {
    let mut guard = pool::ALL_CONNS.write().await;
    if let Some(connection) = guard.get_mut(&id) {
        let mut target_index = None;
        for (index, val) in connection.transactions.iter().enumerate() {
            if val.id == tx_id {
                target_index = Some(index)
            }
        }
        if let Some(target_index) = target_index {
            connection.transactions.remove(target_index);
        }
    }
}

#[tauri::command]
pub(crate) async fn sml_swap_transaction(id: u64, from_tx_id: String, to_tx_id: String) {
    let mut guard = pool::ALL_CONNS.write().await;
    if let Some(connection) = guard.get_mut(&id) {
        let mut from_index = None;
        let mut to_index = None;
        for (index, val) in connection.transactions.iter().enumerate() {
            if val.id == from_tx_id {
                from_index = Some(index)
            }
            if val.id == to_tx_id {
                to_index = Some(index)
            }
        }
        if let Some(from) = from_index {
            if let Some(to) = to_index {
                connection.transactions.swap(from, to);
            }
        }
    }
}

#[tauri::command]
pub(crate) async fn sml_get_configuration(id: u64) -> AppResult<SMLResult<ConnectionOptions>> {
    let guard = pool::ALL_CONNS.read().await;
    if let Some(connection) = guard.get(&id) {
        return Ok(SMLResult {
            is_open: connection.client.is_some(),
            data: connection.configuration.clone(),
        });
    }
    Err(AppError::Message("couldn't find configuration".to_string()))
}

#[tauri::command]
pub(crate) async fn sml_set_configuration(
    id: u64,
    mut configuration: ConnectionOptions,
) -> AppResult<String> {
    let mut guard = pool::ALL_CONNS.write().await;
    // 处理名字重复
    check_configuration_identity(&*guard, &id, &mut configuration);
    let name = configuration.identity.clone();
    if let Some(connection) = guard.get_mut(&id) {
        connection.configuration = configuration;
        return Ok(name);
    }
    Err(AppError::Message("couldn't find configuration".to_string()))
}

#[tauri::command]
pub(crate) async fn sml_get_preference(id: u64) -> AppResult<SMLResult<Preference>> {
    let guard = pool::ALL_CONNS.read().await;
    if let Some(connection) = guard.get(&id) {
        return Ok(SMLResult {
            is_open: connection.client.is_some(),
            data: (*connection.preference).clone(),
        });
    }
    Err(AppError::Message("couldn't find preference".to_string()))
}

#[tauri::command]
pub(crate) async fn sml_set_preference(id: u64, preference: Preference) -> AppResult<()> {
    let mut guard = pool::ALL_CONNS.write().await;
    if let Some(connection) = guard.get_mut(&id) {
        connection.preference = Arc::new(preference);
        return Ok(());
    }
    Err(AppError::Message("couldn't find preference".to_string()))
}

#[tauri::command]
pub(crate) async fn sml_open_log_dir(app: AppHandle, id: u64) {
    let mut to_path = None;
    let guard = pool::ALL_CONNS.read().await;
    if let Some(connection) = guard.get(&id) {
        to_path = Some(connection.preference.log.dir.clone());
    }
    if let Some(path) = to_path.take() {
        match PathBuf::from_str(&path) {
            Ok(mut path_n) => {
                crate::fill_path_internal(&mut path_n);
                if let Some(str_path) = path_n.to_str() {
                    _ = app.opener().open_path(str_path, None::<&str>);
                }
            }
            Err(_) => {}
        }
    }
}

fn check_configuration_identity(
    data: &BTreeMap<u64, pool::QuicConnection>,
    id: &u64,
    configuration: &mut ConnectionOptions,
) {
    let mut is_exists = false;
    for (conn_id, conn_data) in data.iter() {
        if conn_id == id {
            continue;
        }
        if conn_data.configuration.identity == configuration.identity {
            is_exists = true;
            break;
        }
    }
    if is_exists {
        let mut index = 1;
        let mut new_identity = format!("{}({index})", configuration.identity);
        loop {
            is_exists = false;
            for (conn_id, conn_data) in data.iter() {
                if conn_id == id {
                    continue;
                }
                if conn_data.configuration.identity == new_identity {
                    is_exists = true;
                    break;
                }
            }
            if !is_exists {
                break;
            }
            index += 1;
            new_identity = format!("{}({index})", configuration.identity);
        }
        configuration.identity = new_identity;
    }
}
